package cn.code4java.springbok;

import cn.code4java.springbok.config.DirConfig;
import cn.code4java.springbok.config.GenTemplateConfig;
import cn.code4java.springbok.config.GenTemplate;
import cn.code4java.springbok.config.GenTemplateParam;
import cn.code4java.springbok.entity.Table;
import cn.code4java.springbok.entity.TableColumn;
import cn.code4java.springbok.enums.DBJavaTypeMapEnum;
import cn.code4java.springbok.enums.GenTemplateEnum;
import cn.code4java.springbok.enums.ModeEnum;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.io.FileUtils;
import cn.code4java.springbok.utils.StringUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import java.io.File;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * @ClassName CodeGen
 * @Description: TODO
 * @Author fengwensheng
 * @Date 2024/7/8
 * @Version V2.0.0
 **/
public class CodeGenerator {

    private static final String BACKEND_TEMPLATE_PATH = "template/backend";
    private static final String FRONTEND_TEMPLATE_PATH = "template/frontend";

    private GenTemplateConfig config;
    private GenTemplateParam templateParam;

    public CodeGenerator(GenTemplateConfig config) {
        this.config = config;
    }

    public GenTemplateParam getTemplateParam() {
        return templateParam;
    }

    public void setTemplateParam(Table table, List<TableColumn> tableColumns) throws Exception {
        this.templateParam = buildGenTemplateParam(table, tableColumns);
    }

    public GenTemplateParam buildGenTemplateParam(Table table, List<TableColumn> tableColumns) throws Exception {
        GenTemplateParam genTemplateParam = new GenTemplateParam();
        genTemplateParam.setTableName(table.getTableName());
        genTemplateParam.setClassName(table.getClassName());
        genTemplateParam.setDescription(table.getTableComment());
        genTemplateParam.setAuthor(this.config.getAuthor());
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
        genTemplateParam.setDate(LocalDate.now().format(dateTimeFormatter));
        genTemplateParam.setVersion(this.config.getVersion());
        genTemplateParam.setPackageName(this.config.getPackageName());
        Set<String> importTypeSet = new HashSet<>();
        for (TableColumn tableColumn : tableColumns) {
            DBJavaTypeMapEnum dbJavaTypeMapEnum = DBJavaTypeMapEnum.getDBJavaTypeMapEnum(tableColumn.getColumnType());
            tableColumn.setFieldType(dbJavaTypeMapEnum.getJavaType());
            importTypeSet.add(dbJavaTypeMapEnum.getPackageName() + "." + dbJavaTypeMapEnum.getJavaType());
        }
        genTemplateParam.setTableColumnList(tableColumns);
        genTemplateParam.setImportTypeSet(importTypeSet);
        return genTemplateParam;
    }

    public Map<String, Object> getTemplateParams(GenTemplateParam genTable) throws IllegalAccessException {
        Map<String, Object> params = new HashMap<>();
        Class<GenTemplateParam> clazz = GenTemplateParam.class;
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            params.put(field.getName(), field.getName());
            params.put(field.getName(), field.get(genTable));
        }
        return params;
    }

    public void generate() {
        Properties p = new Properties();
        try {
            // 加载classpath目录下的vm文件
            p.setProperty("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
            // 定义字符集
            p.setProperty(Velocity.INPUT_ENCODING, Velocity.ENCODING_DEFAULT);
            // 初始化Velocity引擎，指定配置Properties
            Velocity.init(p);
            VelocityContext velocityContext = new VelocityContext(getTemplateParams(this.templateParam));
            // 渲染模板
            List<GenTemplate> templates = getTemplates();
            for (GenTemplate template : templates) {
                StringWriter sw = new StringWriter();
                String templatePath = template.getPath() + template.getName();
                Template tpl = Velocity.getTemplate(templatePath, Velocity.ENCODING_DEFAULT);
                tpl.merge(velocityContext, sw);
                String outPath = template.getOutPath() + getClassName(template, this.templateParam.getTableName());
                FileUtils.writeStringToFile(new File(outPath), sw.toString(), Velocity.ENCODING_DEFAULT);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private String getClassName(GenTemplate template, String tableName) throws Exception {
        GenTemplateEnum genTemplateEnum = GenTemplateEnum.getGenTemplateEnum(template.getName());
        ModeEnum modeEnum = ModeEnum.valueOf(template.getDirConfig().getFileNameMode());
        if (modeEnum.name().equals(ModeEnum.CAMEL_CASE.name())) {
            tableName = StringUtils.underlineToCamelCase(tableName);
        } else if (modeEnum.name().equals(ModeEnum.PASCAL_CASE.name())) {
            tableName = StringUtils.underlineToPascalCase(tableName);
        }
        return tableName + genTemplateEnum.getSuffix() + "." + genTemplateEnum.getType();
    }

    private List<GenTemplate> getTemplates() {
        List<GenTemplate> templates = Lists.newArrayList();
        // 后端模板
        templates.add(new GenTemplate.Builder()
                .path(BACKEND_TEMPLATE_PATH + "/" + GenTemplateEnum.TEMPLATE_MYSQL.getDir() + "/")
                .name(GenTemplateEnum.TEMPLATE_MYSQL.getTemplate())
                .outPath(this.config.getOutPath() + "/" + this.config.getSqlDir().getDirName() + "/")
                .dirConfig(this.config.getEntityDir()).build());
        templates.add(new GenTemplate.Builder()
                .path(BACKEND_TEMPLATE_PATH + "/" + GenTemplateEnum.TEMPLATE_ENTITY.getDir() + "/")
                .name(GenTemplateEnum.TEMPLATE_ENTITY.getTemplate())
                .outPath(this.config.getOutPath() + "/" + this.config.getEntityDir().getDirName() + "/")
                .dirConfig(this.config.getEntityDir()).build());
        templates.add(new GenTemplate.Builder()
                .path(BACKEND_TEMPLATE_PATH + "/" + GenTemplateEnum.TEMPLATE_DTO.getDir() + "/")
                .name(GenTemplateEnum.TEMPLATE_DTO.getTemplate())
                .outPath(this.config.getOutPath() + "/" + this.config.getDtoDir().getDirName() + "/")
                .dirConfig(this.config.getDtoDir()).build());
        templates.add(new GenTemplate.Builder()
                .path(BACKEND_TEMPLATE_PATH + "/" + GenTemplateEnum.TEMPLATE_VO.getDir() + "/")
                .name(GenTemplateEnum.TEMPLATE_VO.getTemplate())
                .outPath(this.config.getOutPath() + "/" + this.config.getVoDir().getDirName() + "/")
                .dirConfig(this.config.getVoDir()).build());
        templates.add(new GenTemplate.Builder()
                .path(BACKEND_TEMPLATE_PATH + "/" + GenTemplateEnum.TEMPLATE_CONTROLLER.getDir() + "/")
                .name(GenTemplateEnum.TEMPLATE_CONTROLLER.getTemplate())
                .outPath(this.config.getOutPath() + "/" + this.config.getControllerDir().getDirName() + "/")
                .dirConfig(this.config.getControllerDir()).build());
        templates.add(new GenTemplate.Builder()
                .path(BACKEND_TEMPLATE_PATH + "/" + GenTemplateEnum.TEMPLATE_SERVICE.getDir() + "/")
                .name(GenTemplateEnum.TEMPLATE_SERVICE.getTemplate())
                .outPath(this.config.getOutPath() + "/" + this.config.getServiceDir().getDirName() + "/")
                .dirConfig(this.config.getServiceDir()).build());
        templates.add(new GenTemplate.Builder()
                .path(BACKEND_TEMPLATE_PATH + "/" + GenTemplateEnum.TEMPLATE_SERVICE_IMPL.getDir() + "/")
                .name(GenTemplateEnum.TEMPLATE_SERVICE_IMPL.getTemplate())
                .outPath(this.config.getOutPath() + "/" + this.config.getServiceImplDir().getDirName() + "/")
                .dirConfig(this.config.getServiceImplDir()).build());
        templates.add(new GenTemplate.Builder()
                .path(BACKEND_TEMPLATE_PATH + "/" + GenTemplateEnum.TEMPLATE_MAPPER.getDir() + "/")
                .name(GenTemplateEnum.TEMPLATE_MAPPER.getTemplate())
                .outPath(this.config.getOutPath() + "/" + this.config.getMapperDir().getDirName() + "/")
                .dirConfig(this.config.getMapperDir()).build());
        templates.add(new GenTemplate.Builder()
                .path(BACKEND_TEMPLATE_PATH + "/" + GenTemplateEnum.TEMPLATE_MAPPER_XML.getDir() + "/")
                .name(GenTemplateEnum.TEMPLATE_MAPPER_XML.getTemplate())
                .outPath(this.config.getOutPath() + "/" + this.config.getMapperXmlDir().getDirName() + "/")
                .dirConfig(this.config.getMapperXmlDir()).build());
        // 前端模板
        templates.add(new GenTemplate.Builder()
                .path(FRONTEND_TEMPLATE_PATH + "/" + GenTemplateEnum.TEMPLATE_VUE3.getDir() + "/")
                .name(GenTemplateEnum.TEMPLATE_VUE3.getTemplate())
                .outPath(this.config.getOutPath() + "/" + this.config.getVue3Dir().getDirName() + "/")
                .dirConfig(this.config.getVue3Dir()).build());
        templates.add(new GenTemplate.Builder()
                .path(FRONTEND_TEMPLATE_PATH + "/" + GenTemplateEnum.TEMPLATE_API_JS.getDir() + "/")
                .name(GenTemplateEnum.TEMPLATE_API_JS.getTemplate())
                .outPath(this.config.getOutPath() + "/" + this.config.getApiJsDir().getDirName() + "/")
                .dirConfig(this.config.getApiJsDir()).build());
        return templates;
    }
}
